package app.coronawarn.server.services.submission.checkins;

import static app.coronawarn.server.common.persistence.service.utils.checkins.CheckinsDateSpecification.TEN_MINUTE_INTERVAL_DERIVATION;
import static java.time.ZoneOffset.UTC;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import app.coronawarn.server.common.persistence.domain.config.PreDistributionTrlValueMappingProvider;
import app.coronawarn.server.common.persistence.domain.config.TransmissionRiskValueMapping;
import app.coronawarn.server.common.protocols.internal.pt.CheckIn;
import app.coronawarn.server.services.submission.config.SubmissionServiceConfig;
import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.Payload;
import app.coronawarn.server.services.submission.config.SubmissionServiceConfig.Payload.Checkins;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class EventCheckinDataFilterTest {

  private static final int ACCEPTABLE_EVENT_DATE_THRESHOLD_IN_DAYS = 15;

  private EventCheckinDataFilter underTest;
  private SubmissionServiceConfig mockConfig;

  @BeforeEach
  public void setup() {
    mockConfig = new SubmissionServiceConfig();
    Payload payloadConfig = new Payload();
    payloadConfig.setCheckins(new Checkins());
    mockConfig.setPayload(payloadConfig);
    mockConfig.setAcceptedEventDateThresholdDays(ACCEPTABLE_EVENT_DATE_THRESHOLD_IN_DAYS);
    mockConfig.setMaxAllowedCheckinsPerDay(5);

    underTest =
        new EventCheckinDataFilter(mockConfig, createMockTranmissionRiskLevelMappingProvider());
  }

  @ParameterizedTest
  @ValueSource(ints = {ACCEPTABLE_EVENT_DATE_THRESHOLD_IN_DAYS, 16, 19, 20})
  void should_filter_out_checkins_for_old_events(int daysInThePast) {
    Instant thisTimeInstant = Instant.now();
    long eventCheckoutInThePast =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(daysInThePast).toEpochSecond(UTC);

    long acceptableEventCheckoutDate =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(1).toEpochSecond(UTC);

    List<CheckIn> checkins = List.of(
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
            .setTransmissionRiskLevel(1)
            .build(),
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate))
            .setTransmissionRiskLevel(3).build());

    List<CheckIn> result = underTest.filter(checkins);
    assertEquals(1, result.size());
    CheckIn filteredCheckin = result.iterator().next();
    assertEquals(filteredCheckin.getStartIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate - 10));
    assertEquals(filteredCheckin.getEndIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate));
    assertEquals(3, filteredCheckin.getTransmissionRiskLevel());
  }

  @Test
  void should_filter_out_checkins_which_map_to_zero_trl() {
    Instant thisTimeInstant = Instant.now();
    long eventCheckoutInThePast =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(10).toEpochSecond(UTC);

    List<CheckIn> checkins = List.of(
        CheckIn.newBuilder()
            .setStartIntervalNumber(
                TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
            .setTransmissionRiskLevel(1).build(),
        CheckIn.newBuilder()
            .setStartIntervalNumber(
                TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
            .setTransmissionRiskLevel(2).build(),
        CheckIn.newBuilder()
            .setStartIntervalNumber(
                TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
            .setTransmissionRiskLevel(3).build());

    List<CheckIn> result = underTest.filter(checkins);
    assertEquals(1, result.size());
    CheckIn filteredCheckin = result.iterator().next();
    assertEquals(filteredCheckin.getStartIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10));
    assertEquals(filteredCheckin.getEndIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast));
    assertEquals(3, filteredCheckin.getTransmissionRiskLevel());
  }


  @ParameterizedTest
  @ValueSource(ints = {ACCEPTABLE_EVENT_DATE_THRESHOLD_IN_DAYS - 1,
      ACCEPTABLE_EVENT_DATE_THRESHOLD_IN_DAYS - 2})
  void should_not_filter_checkins_of_events_with_acceptable_date_thresholds(int daysInThePast) {
    Instant thisTimeInstant = Instant.now();
    long eventCheckoutInThePast =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(daysInThePast).toEpochSecond(UTC);

    long acceptableEventCheckoutDate =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(1).toEpochSecond(UTC);

    List<CheckIn> checkins = List.of(
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
            .setTransmissionRiskLevel(3)
            .build(),
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate - 10))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(acceptableEventCheckoutDate))
            .setTransmissionRiskLevel(3).build());

    List<CheckIn> result = underTest.filter(checkins);
    assertEquals(2, result.size());
  }

  @Test
  void should_filter_out_checkins_for_future_events() {
    Instant thisTimeInstant = Instant.now();
    long eventCheckinInTheFuture =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).plusMinutes(11).toEpochSecond(UTC);

    long eventCheckinInTheNearPast =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusMinutes(10).toEpochSecond(UTC);

    List<CheckIn> checkins = List.of(
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheFuture))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheFuture + 5))
            .setTransmissionRiskLevel(1).build(),
        CheckIn.newBuilder()
            .setStartIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheNearPast))
            .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheNearPast + 10))
            .setTransmissionRiskLevel(3).build());

    List<CheckIn> result = underTest.filter(checkins);
    assertEquals(1, result.size());
    CheckIn filteredCheckin = result.iterator().next();
    assertEquals(filteredCheckin.getStartIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheNearPast));
    assertEquals(filteredCheckin.getEndIntervalNumber(),
        TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckinInTheNearPast + 10));
    assertEquals(3, filteredCheckin.getTransmissionRiskLevel());
  }

  @Test
  void should_abort_the_checkins_submission_when_too_many_checkins() {
    Instant thisTimeInstant = Instant.now();
    long eventCheckoutInThePast =
        LocalDateTime.ofInstant(thisTimeInstant, UTC).minusDays(10).toEpochSecond(UTC);

    List<CheckIn> checkins = new ArrayList<>();
    for (int i = 0; i <= mockConfig.getMaxAllowedCheckinsPerDay(); i++) {
      checkins.add(CheckIn.newBuilder()
          .setStartIntervalNumber(
              TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast - 10))
          .setEndIntervalNumber(TEN_MINUTE_INTERVAL_DERIVATION.apply(eventCheckoutInThePast))
          .setTransmissionRiskLevel(1).build());
    }

    assertThrows(TooManyCheckInsAtSameDay.class, () -> underTest.validateCheckInsByDate(checkins));

  }

  private PreDistributionTrlValueMappingProvider createMockTranmissionRiskLevelMappingProvider() {
    TransmissionRiskValueMapping mapsToZero = new TransmissionRiskValueMapping();
    mapsToZero.setTransmissionRiskLevel(1);
    mapsToZero.setTransmissionRiskValue(0.0d);
    TransmissionRiskValueMapping mapsToZero2 = new TransmissionRiskValueMapping();
    mapsToZero2.setTransmissionRiskLevel(2);
    mapsToZero2.setTransmissionRiskValue(0.0d);
    TransmissionRiskValueMapping mapsToOtherThanZero = new TransmissionRiskValueMapping();
    mapsToOtherThanZero.setTransmissionRiskLevel(3);
    mapsToOtherThanZero.setTransmissionRiskValue(1.5d);

    PreDistributionTrlValueMappingProvider provider = new PreDistributionTrlValueMappingProvider();
    provider.setTransmissionRiskValueMapping(List.of(mapsToZero, mapsToZero2, mapsToOtherThanZero));
    return provider;
  }
}
